﻿using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using System.Xml;
using Sasa.QualityTools.CodeAnalysis.Common;

namespace Sasa.QualityTools.CodeAnalysis.Core.Models
{
    public class FxCopReport : ICodeAnalysisReport
    {
        private object loadLockKey;


        public ICollection<Problem> Problems { get; private set; }

        public ICollection<Rule> Rules { get; private set; }

        public ICollection<CodeAnalysisError> Errors { get; private set; }

        public ICollection<IFxCopReportValueCoordinator> Coordinators { get; private set; }

        public ICodeAnalysisResultValueFilter Filter { get; set; }


        public FxCopReport()
        {
            loadLockKey = new object();
            Problems = new List<Problem>();
            Rules = new List<Rule>();
            Errors = new List<CodeAnalysisError>();
            Coordinators = new List<IFxCopReportValueCoordinator>();
        }


        public void Load(string path)
        {
            if (!File.Exists(path))
            {
                var message = Text.Format("The specified file '{0}' doesn't exist.", path);
                throw new InvalidOperationException(message);
            }

            try
            {
                lock (loadLockKey)
                {
                    Problems.Clear();
                    Rules.Clear();
                    Errors.Clear();

                    var document = new XmlDocument();
                    document.Load(path);
                    LoadProblems(document);
                    LoadRules(document);
                    LoadAnalysisErrors(document);

                    foreach (var coordinator in Coordinators)
                    {
                        foreach (var problem in Problems)
                        {
                            coordinator.Coordinate(problem);
                        }
                    }
                }
            }
            catch (Exception e)
            {
                var message = Text.Format("Failed to load the specified FxCop report '{0}'.", path);
                throw new CodeAnalysisException(message, e);
            }
        }

        internal void LoadProblems(XmlNode root)
        {
            foreach (var messageNode in root.SelectNodes("//Message").Cast<XmlNode>())
            {
                var template = BuildupProblemTemplate(messageNode);

                foreach (XmlNode node in messageNode.SelectNodes("Issue"))
                {
                    var problem = template.Copy();
                    if (node.Attributes["Path"] != null && node.Attributes["File"] != null)
                    {
                        problem.Path = Path.Combine(node.Attributes["Path"].Value, node.Attributes["File"].Value);
                    }
                    if (node.Attributes["Line"] != null)
                    {
                        problem.Line = Int32.Parse(node.Attributes["Line"].Value, CultureInfo.InvariantCulture);
                    }
                    problem.Message = node.InnerText;

                    if (Filter == null || Filter.IsAvailable(problem))
                    {
                        Problems.Add(problem);
                    }
                }
            }
        }

        internal Problem BuildupProblemTemplate(XmlNode messageNode)
        {
            var template = new Problem();
            template.CheckId = messageNode.Attributes["CheckId"].Value;
            template.Category = messageNode.Attributes["Category"].Value;
            template.Id = messageNode.SelectSingleNode("../../@Name").Value;
            template.Kind = GetCodeKind(messageNode);

            if (IsType(template.Kind))
            {
                string ns = messageNode.SelectSingleNode("ancestor::Namespace/@Name").Value;
                template.TypeName = Text.Format("{0}.{1}", ns, template.Id);
            }
            else if (IsMember(template.Kind))
            {
                string ns = messageNode.SelectSingleNode("ancestor::Namespace/@Name").Value;
                string type = messageNode.SelectSingleNode("ancestor::Type/@Name").Value;
                template.TypeName = Text.Format("{0}.{1}", ns, type);
            }

            template.AssemblyPath = messageNode.SelectSingleNode("ancestor::Target/@Name").GetValueOrDefault(null);

            return template;
        }

        internal void LoadRules(XmlNode root)
        {
            foreach (XmlNode node in root.SelectNodes("/FxCopReport/Rules/Rule"))
            {
                var rule = BuildupRule(node);
                Rules.Add(rule);
            }
        }

        internal Rule BuildupRule(XmlNode ruleNode)
        {
            if (ruleNode == null)
            {
                throw new ArgumentNullException("ruleNode");
            }

            return new Rule()
            {
                CheckId = ruleNode.Attributes["CheckId"].GetValueOrDefault(""),
                Category = ruleNode.Attributes["Category"].GetValueOrDefault(""),
                TypeName = ruleNode.Attributes["TypeName"].GetValueOrDefault(""),
                Name = ruleNode["Name"].GetInnerTextOrDefault(""),
                Description = ruleNode["Description"].GetInnerTextOrDefault(""),
                Url = ruleNode["Url"].GetInnerTextOrDefault(""),
            };
        }

        internal void LoadAnalysisErrors(XmlNode root)
        {
            foreach (XmlNode node in root.SelectNodes("/FxCopReport/Exceptions/Exception"))
            {
                var error = BuildupAnalysisError(node);
                Errors.Add(error);
            }
        }

        internal CodeAnalysisError BuildupAnalysisError(XmlNode exceptionNode)
        {
            if (exceptionNode == null)
            {
                throw new ArgumentNullException("exceptionNode");
            }

            return new CodeAnalysisError
            {
                Id = exceptionNode.Attributes["Keyword"].GetValueOrDefault(""),
                Message = exceptionNode["ExceptionMessage"].GetInnerTextOrDefault(""),
            };
        }

        [SuppressMessage("Microsoft.Usage", "CA1806")]
        private CodeKind GetCodeKind(XmlNode messageNode)
        {
            CodeKind kind = CodeKind.Unknown;

            XmlNode kindAttr = messageNode.SelectSingleNode("../../@Kind");
            if (kindAttr != null)
            {
                Enum.TryParse<CodeKind>(kindAttr.Value, true, out kind);
            }
            else
            {
                Enum.TryParse<CodeKind>(messageNode.SelectSingleNode("../..").Name, true, out kind);
            }

            return kind;
        }

        private bool IsType(CodeKind kind)
        {
            return kind == CodeKind.Class || kind == CodeKind.Delegate || kind == CodeKind.Enum || kind == CodeKind.Interface || kind == CodeKind.Struct;
        }

        private bool IsMember(CodeKind kind)
        {
            return kind == CodeKind.Constructor || kind == CodeKind.Event || kind == CodeKind.Field || kind == CodeKind.Method || kind == CodeKind.Property;
        }
    }
}
